﻿/*
Copyright (c) 2010 Lance Ward
This software is licensed under the MIT license, except where otherwise noted.
For complete license details, refer to license.txt
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ADNColorPicker
{
	public enum ColorRangeType
	{
		A,
		R,
		G,
		B
	}

	public partial class ColorRange : UserControl
	{
		private Color baseColor = Color.White;
		private int markerWidth = 7;
		private int markerHeight = 7;
		private ColorRangeType type = ColorRangeType.R;
		private bool capturing = false;
		public event EventHandler ColorChanged;
		private Bitmap gradBmp;

		public ColorRange()
		{
			InitializeComponent();
			CreateGradientBitmap();
		}

		public Color BaseColor
		{
			get { return baseColor; }
			set
			{
				baseColor = value;
				CreateGradientBitmap();
				this.Invalidate();
			}
		}

		public ColorRangeType Type
		{
			get { return type; }
			set
			{
				type = value;
				CreateGradientBitmap();
				this.Invalidate();
			}
		}

		public int Value
		{
			get { return GetValue(); }
			set { SetValue(value); }	//does not trigger an ColorChanged event
		}

		private int GetValue()
		{
			switch (type)
			{
				case ColorRangeType.A:
					return baseColor.A;
				case ColorRangeType.R:
					return baseColor.R;
				case ColorRangeType.G:
					return baseColor.G;
				case ColorRangeType.B:
					return baseColor.B;
				default:
					return 0;
			}
		}

		private void SetValue(int val)
		{
			if (val < 0 || val > 255) { throw new ArgumentOutOfRangeException("Value", val, "Value must be > 0 and < 255"); }

			switch (type)
			{
				case ColorRangeType.A:
					baseColor = Color.FromArgb(val, baseColor.R, baseColor.G, baseColor.B);
					break;
				case ColorRangeType.R:
					baseColor = Color.FromArgb(baseColor.A, val, baseColor.G, baseColor.B);
					break;
				case ColorRangeType.G:
					baseColor = Color.FromArgb(baseColor.A, baseColor.R, val, baseColor.B);
					break;
				case ColorRangeType.B:
					baseColor = Color.FromArgb(baseColor.A, baseColor.R, baseColor.G, val);
					break;
			}

			this.Invalidate();
		}

		private void OnColorChanged()
		{
			if (ColorChanged != null) { ColorChanged(this, EventArgs.Empty); }
		}

		private void CreateGradientBitmap()
		{
			if (gradBmp != null)
			{
				gradBmp.Dispose();
				gradBmp = null;
			}

			gradBmp = new Bitmap(this.Width, this.Height);
			Graphics g = Graphics.FromImage(gradBmp);
			g.Clear(Color.Transparent);
			DrawGradient(GetGradientRect(), g);
		}

		private void GradientRange_Paint(object sender, PaintEventArgs e)
		{
			e.Graphics.Clear(Color.White);
			Draw(e.Graphics);
		}

		private void Draw(Graphics g)
		{
			g.CompositingQuality = CompositingQuality.HighQuality;
			g.DrawImage(gradBmp, new Point(0, 0));
			DrawMarker(GetGradientRect(), g);
		}

		private Rectangle GetGradientRect()
		{
			Rectangle r = this.ClientRectangle;
			r.Inflate(-(markerWidth / 2), -(markerHeight / 2));
			return r;
		}

		private void DrawGradient(Rectangle r, Graphics g)
		{
			//draw a gradient from color A = 255 to color A = 0
			Brush b;

			switch (type)
			{
				case ColorRangeType.A:
					b = GetBrushA(r);
					break;
				case ColorRangeType.R:
					b = GetBrushR(r);
					break;
				case ColorRangeType.G:
					b = GetBrushG(r);
					break;
				case ColorRangeType.B:
					b = GetBrushB(r);
					break;
				default:
					b = new SolidBrush(Color.White);
					break;
			}

			if (type == ColorRangeType.A) { g.FillRectangle(new HatchBrush(HatchStyle.LargeCheckerBoard, Color.LightGray, Color.Gray), r); }
			g.FillRectangle(b, r);
			g.DrawRectangle(new Pen(Color.DarkGray), r);
		}

		private Brush GetBrushA(Rectangle r)
		{
			Color startColor = Color.FromArgb(255, baseColor);
			Color endColor = Color.FromArgb(0, baseColor);
			Brush b = new LinearGradientBrush(r, startColor, endColor, 180);

			return b;
		}

		private Brush GetBrushR(Rectangle r)
		{
			Color startColor = Color.FromArgb(255, baseColor.G, baseColor.B);
			Color endColor = Color.FromArgb(0, baseColor.G, baseColor.B);
			Brush b = new LinearGradientBrush(r, startColor, endColor, 180);

			return b;
		}

		private Brush GetBrushG(Rectangle r)
		{
			Color startColor = Color.FromArgb(baseColor.R, 255, baseColor.B);
			Color endColor = Color.FromArgb(baseColor.R, 0, baseColor.B);
			Brush b = new LinearGradientBrush(r, startColor, endColor, 180);

			return b;
		}

		private Brush GetBrushB(Rectangle r)
		{
			Color startColor = Color.FromArgb(baseColor.R, baseColor.G, 255);
			Color endColor = Color.FromArgb(baseColor.R, baseColor.G, 0);
			Brush b = new LinearGradientBrush(r, startColor, endColor, 180);

			return b;
		}


		private void DrawMarker(Rectangle r, Graphics g)
		{
			float position;

			switch (type)
			{
				case ColorRangeType.A:
					position = GetMarkerPosition(r, baseColor.A);
					break;
				case ColorRangeType.R:
					position = GetMarkerPosition(r, baseColor.R);
					break;
				case ColorRangeType.G:
					position = GetMarkerPosition(r, baseColor.G);
					break;
				case ColorRangeType.B:
					position = GetMarkerPosition(r, baseColor.B);
					break;
				default:
					position = 0.0f;
					break;
			}

			Rectangle clientR = this.ClientRectangle;
			//Draw the triangle
			PointF apex = new PointF(position, clientR.Bottom - markerHeight);
			PointF left = new PointF(position - (markerWidth / 2), clientR.Bottom);
			PointF right = new PointF(position + (markerWidth / 2), clientR.Bottom);

			GraphicsPath gp = new GraphicsPath();
			gp.AddLine(apex, left);
			gp.AddLine(left, right);
			gp.AddLine(right, apex);

			g.FillPath(new SolidBrush(Color.Black), gp);
		}

		private float GetMarkerPosition(Rectangle r, int argbPosition)
		{
			//divide gradient rect by 255 & multiply by baseColor.x
			float mult = (float)(r.Width) / 255.0f;
			float pos = argbPosition * mult;
			return pos + r.X;
		}

		private void GradientRange_MouseDown(object sender, MouseEventArgs e)
		{
			capturing = true;
			SetColorByPosition(e.Location);
			//reposition marker and redraw, but don't trigger
		}

		private void GradientRange_MouseMove(object sender, MouseEventArgs e)
		{
			if (capturing)
			{
				SetColorByPosition(e.Location);
			}
			//redraw but don't trigger
		}

		private void GradientRange_MouseUp(object sender, MouseEventArgs e)
		{
			SetColorByPosition(e.Location);
			capturing = false;
			//finalize position and color and trigger
		}

		private void GradientRange_MouseLeave(object sender, EventArgs e)
		{
			capturing = false;
		}

		private void SetColorByPosition(Point clickPoint)
		{
			switch (type)
			{
				case ColorRangeType.A:
					int A = FindColor(clickPoint);
					baseColor = Color.FromArgb(A >= 0 ? A : baseColor.A, baseColor.R, baseColor.G, baseColor.B);
					break;
				case ColorRangeType.R:
					int R = FindColor(clickPoint);
					baseColor = Color.FromArgb(baseColor.A, R >= 0 ? R : baseColor.R, baseColor.G, baseColor.B);
					break;
				case ColorRangeType.G:
					int G = FindColor(clickPoint);
					baseColor = Color.FromArgb(baseColor.A, baseColor.R, G >= 0 ? G : baseColor.G, baseColor.B);
					break;
				case ColorRangeType.B:
					int B = FindColor(clickPoint);
					baseColor = Color.FromArgb(baseColor.A, baseColor.R, baseColor.G, B >= 0 ? B : baseColor.B);
					break;
				default:
					break;
			}

			OnColorChanged();	//fire event
			this.Invalidate();
		}

		private int FindColor(Point clickPoint)
		{
			Rectangle r = GetGradientRect();

			if (r.Contains(clickPoint))
			{
				float mult = (float)(clickPoint.X - r.X) / (float)(r.Width);
				int col = (int)Math.Ceiling(255 * mult);
				return col;
			}
			else  //the event occurred on the control, but outside the gradient strip, return min & max
			{
				if (clickPoint.X > (r.X + r.Width)) { return 255; }
				else if(clickPoint.X < r.X) { return 0; }
			}

			return -1;
		}

		private void GradientRange_Resize(object sender, EventArgs e)
		{
			CreateGradientBitmap();
			this.Invalidate();
		}
	}
}
